Skip to content

fix: MCP auth proxy for transparent JWT refresh in long sessions#268

Merged
dimavedenyapin merged 8 commits intomainfrom
fix/mcp-auth-proxy-token-refresh
Apr 26, 2026
Merged

fix: MCP auth proxy for transparent JWT refresh in long sessions#268
dimavedenyapin merged 8 commits intomainfrom
fix/mcp-auth-proxy-token-refresh

Conversation

@dimavedenyapin
Copy link
Copy Markdown
Contributor

@dimavedenyapin dimavedenyapin commented Apr 25, 2026

Summary

  • MCP auth proxy (mcp-auth-proxy.ts): local HTTP reverse proxy on 127.0.0.1:<random port> that intercepts every MCP Streamable HTTP request, calls enterpriseAuth.getJwt() for a fresh JWT, and forwards to the real MCP server. Decouples token lifetime from session lifetime.
  • Agent manager: routes [Workflo] MCP Dev Server through the proxy instead of baking a static JWT at session start. Falls back to static JWT if proxy isn't running.
  • Startup + login: proxy starts both on enterprise session restore (index.ts) and on fresh login (ipc-handlers.ts), so it works regardless of startup state.
  • 14 unit tests covering forwarding, JWT injection, dedup, SSE streaming, concurrent requests, error handling.

Root cause of "errors that don't leave"

JWT has 1-hour lifetime. ACP/OpenCode/Claude Code bake MCP headers at session start and never refresh them. After ~55 minutes every MCP tool call gets 401 with no recovery path — the proxy fixes this transparently.

Test plan

  • 15/15 MCP tool calls through proxy — all 200s, fresh JWT per request
  • Verified across all MCP Dev Server categories: workflows, integrations (MongoDB, Firestore), data tables, datastores, tasks
  • 14 unit tests passing
  • TypeScript compiles clean
  • ESLint passes (warnings only, pre-existing)

🤖 Generated with Claude Code

dimavedenyapin and others added 8 commits April 25, 2026 13:40
ACP, OpenCode, and Claude Code all bake MCP server headers at session
start and never refresh them. After ~55 minutes the 1-hour JWT expires,
causing persistent 401 errors on every MCP tool call with no recovery.

This adds a local HTTP reverse proxy (127.0.0.1:<random port>) that
intercepts MCP requests, calls enterpriseAuth.getJwt() for a fresh
token on each request, and forwards to the real MCP server. The proxy
decouples token lifetime from session lifetime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
registerMcpProxyTarget is called on every buildMcpServersForAdapter,
which runs per-prompt for Claude Code. Without dedup, each prompt
creates a new target ID. Now returns the existing proxy URL if the
same target URL was already registered.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two bugs prevented MCP connection through the proxy:

1. Trailing slash: POST to /<id> produced target URL with trailing
   slash (e.g. /api/mcp/dev/mcp/) causing 404. Now uses target base
   URL as-is when there's no sub-path.

2. SSE buffering: deleting transfer-encoding header broke SSE event
   streaming. Now preserves it for text/event-stream responses and
   writes each chunk directly for immediate delivery.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Logs every incoming request, target resolution, upstream URL, and
response status so we can see what's happening when the MCP client
tries to connect through the proxy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The proxy starts correctly but the Claude Code SDK never sends requests
to the proxy URL — zero HTTP requests reach the handler. The SDK
silently fails to establish an MCP transport through the proxy.

Reverting to the original static JWT injection that works. The proxy
code and tests remain in the codebase for future investigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Routes [Workflo] MCP Dev Server through the local auth proxy
(http://127.0.0.1:<port>/<id>) using type: 'http' (Streamable HTTP).
Proxy injects fresh JWT on every request, decoupling token lifetime
from session lifetime.

Added logging of exact MCP config passed to claude binary for debugging.
Added test-mcp-proxy-client.mjs script to manually verify proxy connectivity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The proxy was only started in index.ts during startup session restore.
If the enterprise session wasn't cached (isAuthenticated: false), the
proxy never started — even after the user logged in via ipc-handlers.

Now startMcpAuthProxy() is also called in the enterprise login flow
(ipc-handlers.ts) so it works regardless of startup state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce proxy logs to errors-only (startup + 404 + auth failures).
Remove per-request logging and debug MCP config dump now that the
proxy is verified working end-to-end.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dimavedenyapin dimavedenyapin merged commit 021d57d into main Apr 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant